import fs from 'fs';
import path from 'path';
import process from 'process';
import readline from 'readline';
import {StrUtil} from '../../../utils/StrUtil.js';
import {FileUtil} from '../../../utils/FileUtil.js';
import {AppUtil} from '../../../utils/AppUtil.js';
import {OPSConstant} from '../../../constant/OPSConstant.js';
import {GitUtil} from '../../../utils/GitUtil.js';

export const cloneMixin = {
    buildGitCloneOptions(options = {}) {
        const rawFast = options?.fast ?? options?.f;
        const httpEnabled = this.parseBooleanFlag(options?.http ?? options?.https ?? options?.useHttp, false);
        const sshEnabled = this.parseBooleanFlag(options?.ssh ?? options?.useSsh, false);
        let targetProtocol = null;
        if (httpEnabled) {
            targetProtocol = 'http';
        } else if (sshEnabled) {
            targetProtocol = 'ssh';
        }
        return {
            usePartialClone: this.parseBooleanFlag(rawFast, false),
            targetProtocol
        };
    },

    resolveRepoUrlForClone(repoUrl, cloneOptions = {}) {
        const protocol = cloneOptions?.targetProtocol;
        if (protocol === 'http') {
            return StrUtil.toHttpUrl(repoUrl);
        }
        return StrUtil.toSshUrl(repoUrl);
    },

    isReuseExistingRepoEnabled() {
        if (typeof this.reuseExistingRepoEnabled === 'boolean') {
            return this.reuseExistingRepoEnabled;
        }
        const rawFlag = this.globalOpsFile?.read('clone.reuse_existing_dir');
        this.reuseExistingRepoEnabled = this.parseBooleanFlag(rawFlag, true);
        return this.reuseExistingRepoEnabled;
    },

    findReusableRepoPath(repoName, {searchRoot, targetDir, maxDepth = 5} = {}) {
        if (!this.isReuseExistingRepoEnabled()) {
            return '';
        }
        const normalizedName = (repoName || '').trim();
        if (!normalizedName) {
            return '';
        }
        const normalizedTarget = targetDir ? path.resolve(targetDir) : '';
        if (normalizedTarget) {
            try {
                if (fs.existsSync(normalizedTarget) && fs.lstatSync(normalizedTarget).isDirectory()) {
                    const files = fs.readdirSync(normalizedTarget);
                    if (files.length > 0) {
                        return normalizedTarget;
                    }
                }
            } catch (e) {
                // ignore
            }
        }
        const root = searchRoot ? path.resolve(searchRoot) : '';
        if (!root) {
            return '';
        }
        const cacheKey = `${root}::${normalizedName}`;
        if (this.reuseRepoPathCache.has(cacheKey)) {
            return this.reuseRepoPathCache.get(cacheKey) || '';
        }
        let candidates = [];
        try {
            candidates = FileUtil.findDirsByName(root, normalizedName, {maxDepth, limit: 5});
        } catch (e) {
            candidates = [];
        }
        const filtered = normalizedTarget
            ? candidates.filter(item => path.resolve(item) !== normalizedTarget)
            : candidates;
        const matched = filtered.length > 0 ? filtered[0] : '';
        this.reuseRepoPathCache.set(cacheKey, matched);
        return matched;
    },

    async handleCloneCommand(rawTarget, options = {}) {
        const target = (rawTarget || '').trim();
        if (!target) {
            throw new Error('clone 命令必须提供克隆目标');
        }
        const verbose = Boolean(options.verbose);
        const cloneOptions = this.attachPermissionCollector(this.buildGitCloneOptions(options));
        this.logger.info(`开始处理克隆目标: ${target}`);
        if (cloneOptions.usePartialClone) {
            this.logger.info('已启用快速克隆模式 (--single-branch --depth=1 --filter=blob:none)');
        }
        if (cloneOptions.targetProtocol === 'ssh') {
            this.logger.info('已启用 SSH 协议强制转换，非 SSH 地址将自动转换');
        } else if (cloneOptions.targetProtocol === 'http') {
            this.logger.info('已启用 HTTP 协议强制转换，仓库地址将使用 HTTP 形式克隆');
        }

        try {
            if (await this.tryCloneDirectRepo(target, cloneOptions)) {
                return;
            }
            if (await this.tryCloneFromOpsUrl(target, verbose, cloneOptions)) {
                return;
            }
            if (await this.tryCloneFromAppName(target, verbose, cloneOptions)) {
                return;
            }
            if (await this.tryCloneFromAlias(target, cloneOptions)) {
                return;
            }
            throw new Error(`无法识别 ${target} 对应的仓库信息，请确认输入是否正确`);
        } finally {
            await this.promptPermissionApplyIfNeeded();
            this.flushPendingIdeaOpen();
        }
    },

    attachPermissionCollector(cloneOptions = {}) {
        const base = {...cloneOptions};
        const originalHandler = typeof base.onPermissionDenied === 'function' ? base.onPermissionDenied : null;
        base.onPermissionDenied = (info = {}) => {
            if (info && info.repoUrl) {
                this.recordPermissionDenied(info.repoUrl, info.message);
            }
            if (originalHandler) {
                originalHandler(info);
            }
        };
        return base;
    },

    scheduleOpenIdea(dirPath) {
        if (StrUtil.notNull(dirPath)) {
            this.pendingIdeaOpenPath = dirPath;
        }
    },

    flushPendingIdeaOpen() {
        if (StrUtil.notNull(this.pendingIdeaOpenPath)) {
            AppUtil.openIdea(this.pendingIdeaOpenPath);
            this.pendingIdeaOpenPath = null;
        }
    },

    recordPermissionDenied(repoUrl, message = '') {
        if (!repoUrl) {
            return;
        }
        const normalized = repoUrl.trim();
        if (!normalized) {
            return;
        }
        if (!this.permissionDeniedRepos.has(normalized)) {
            this.permissionDeniedRepos.set(normalized, message || '');
        }
    },

    async promptPermissionApplyIfNeeded() {
        if (this.permissionDeniedRepos.size === 0) {
            return;
        }
        this.logger.warn('以下仓库克隆失败，疑似缺少访问权限:');
        for (const repoUrl of this.permissionDeniedRepos.keys()) {
            this.logger.warn(`- ${repoUrl}`);
        }
        const confirm = await this.askPermissionConfirmation();
        if (!confirm) {
            this.logger.info('已跳过自动申请，可根据列表联系组长或稍后重试。');
            this.permissionDeniedRepos.clear();
            return;
        }
        try {
            await this.submitPermissionApply(Array.from(this.permissionDeniedRepos.keys()));
        } catch (error) {
            this.logger.error(`权限申请失败: ${error.message}`);
        } finally {
            this.permissionDeniedRepos.clear();
        }
    },

    askPermissionConfirmation() {
        return new Promise(resolve => {
            const rl = readline.createInterface({
                input: process.stdin,
                output: process.stdout
            });
            rl.question('是否立即发起权限申请？[y/N] ', answer => {
                rl.close();
                const normalized = (answer || '').trim().toLowerCase();
                if (!normalized) {
                    resolve(false);
                    return;
                }
                resolve(normalized === 'y' || normalized === 'yes');
            });
        });
    },

    getPermissionApplySubject() {
        const candidateKeys = [
            'permission_apply.subject',
            'nb.permission_apply_subject'
        ];
        for (const key of candidateKeys) {
            const value = this.globalOpsFile?.read(key);
            if (StrUtil.notNull(value)) {
                return String(value).trim();
            }
        }
        return '开发需求，申请权限';
    },

    async getPermissionInformMember() {
        if (StrUtil.notNull(this.cachedInformMember)) {
            return this.cachedInformMember;
        }
        const candidateKeys = [
            'permission_apply.inform_member',
            'nb.permission_inform_member',
            'zz_ops_user',
            'ops_leader'
        ];
        for (const key of candidateKeys) {
            const value = this.globalOpsFile?.read(key);
            if (StrUtil.notNull(value)) {
                const normalized = String(value).trim();
                if (normalized) {
                    this.cachedInformMember = normalized;
                    return normalized;
                }
            }
        }
        try {
            await this.ensureOpsClient(false);
            const remoteInformMember = await this.opsClient.request('/app/api/v1/apps/inform_member');
            const normalized = StrUtil.notNull(remoteInformMember) ? String(remoteInformMember).trim() : '';
            if (normalized) {
                this.logger.info(`组长:${normalized}`)
                this.cachedInformMember = normalized;
                return normalized;
            }
        } catch (error) {
            this.logger.warn(`远程查询权限通知人失败，已使用默认值: ${error.message}`);
        }
        return this.cachedInformMember;
    },

    async getExistingPermissionProjects() {
        try {
            await this.ensureOpsClient(false);
            const records = await this.opsClient.request('/app/api/v1/apply/list/asc');
            if (!Array.isArray(records)) {
                return new Set();
            }
            const result = new Set();
            for (const item of records) {
                const status = String(item?.apply_res ?? '').toLowerCase();
                if (!['start', 'pass'].includes(status)) {
                    continue;
                }
                const projectList = Array.isArray(item?.project_list) ? item.project_list : [];
                for (const project of projectList) {
                    if (StrUtil.notNull(project)) {
                        result.add(String(project).trim());
                    }
                }
            }
            return result;
        } catch (error) {
            this.logger.warn(`查询权限申请列表失败: ${error.message}`);
            return new Set();
        }
    },

    async submitPermissionApply(repoUrls) {
        if (!Array.isArray(repoUrls) || repoUrls.length === 0) {
            return;
        }
        await this.ensureOpsClient(false);
        const normalizedRepos = Array.from(new Set(repoUrls.map(repo => (repo || '').trim()).filter(repo => repo)));
        if (normalizedRepos.length === 0) {
            return;
        }
        const existingRepoSet = await this.getExistingPermissionProjects();
        const pendingRepos = normalizedRepos.filter(repo => !existingRepoSet.has(repo));
        const skippedRepos = normalizedRepos.filter(repo => existingRepoSet.has(repo));
        if (skippedRepos.length > 0) {
            this.logger.info(`已有申请记录，跳过重复提交: ${skippedRepos.join(', ')}`);
        }
        if (pendingRepos.length === 0) {
            this.logger.info('所有仓库均已在申请中或已通过，本次无需提交新申请。');
            return;
        }
        const subject = this.getPermissionApplySubject();
        const informMember = await this.getPermissionInformMember();
        this.logger.info(`正在发起权限申请，涉及仓库 ${pendingRepos.length} 个`);
        const payload = {
            write: pendingRepos,
            read: [],
            subject,
            inform_member: informMember
        };
        const response = await this.opsClient.request('/app/api/v1/apply/create/asc/asc', 'POST', payload);
        if (response?.status && response.status !== 'success') {
            throw new Error(response?.message || '申请失败');
        }
        const message = response?.message || '申请成功';
        this.logger.success(`权限申请已提交: ${message}`);
    },

    async tryCloneDirectRepo(target, cloneOptions = {}) {
        let repoUrl = target;
        const looksLikeGitUrl = (value) => /^git@/.test(value) || /^https?:\/\//i.test(value);
        if (!looksLikeGitUrl(repoUrl)) {
            const candidate =
                cloneOptions.targetProtocol === 'ssh'
                    ? StrUtil.toSshUrl(repoUrl)
                    : cloneOptions.targetProtocol === 'http'
                        ? StrUtil.toHttpUrl(repoUrl)
                        : null;
            if (StrUtil.notNull(candidate) && looksLikeGitUrl(candidate)) {
                repoUrl = candidate;
            }
        }
        if (!looksLikeGitUrl(repoUrl)) {
            return false;
        }
        this.logger.info('检测到仓库地址，直接执行 clone');
        return await GitUtil.cloneDirect(repoUrl, null, cloneOptions);
    },

    async tryCloneFromOpsUrl(target, verbose, cloneOptions = {}) {
        const urlMatch = target.match(/^https:\/\/vops\.hszq8\.com\/app\/app\/([^\/\s]+)(?:\/.*)?$/);
        if (!urlMatch) {
            return false;
        }
        const appName = urlMatch[1];
        this.logger.info(`解析出应用名: ${appName}`);
        return await this.cloneByAppName(appName, verbose, cloneOptions);
    },

    async tryCloneFromAppName(target, verbose, cloneOptions = {}) {
        if (StrUtil.containsChinese(target)) {
            return false;
        }
        // 支持数字、字母、横杠、下划线、点
        if (!/^[A-Za-z0-9._-]+$/.test(target)) {
            return false;
        }
        this.logger.info(`尝试根据应用名 ${target} 获取仓库信息`);
        return await this.cloneByAppName(target, verbose, cloneOptions);
    },

    async tryCloneFromAlias(target, cloneOptions = {}) {
        if (!StrUtil.containsChinese(target)) {
            return false;
        }
        const aliasRepos = OPSConstant.NB_CLONE_ALIAS_MAP[target];
        if (!aliasRepos || aliasRepos.length === 0) {
            this.logger.warn(`未找到 ${target} 对应的别名配置`);
            return false;
        }
        this.logger.info(`匹配到别名 ${target}，共 ${aliasRepos.length} 个仓库需要克隆`);
        const codePath = await this.globalOpsFile.read('code_path');
        if (StrUtil.isNull(codePath)) {
            throw new Error("请检查文件 ops_global文件中的 code_path 属性，请维护你期望下载的位置");
        }
        FileUtil.ensureDir(codePath);
        const originalCwd = process.cwd();
        const aliasDir = path.join(codePath, target);
        FileUtil.ensureDir(aliasDir);
        let success = false;
        try {
            process.chdir(aliasDir);
            for (const repo of aliasRepos) {
                const result = await GitUtil.cloneDirect(repo, null, cloneOptions);
                success = success || result;
            }
        } finally {
            process.chdir(originalCwd);
        }
        if (!success) {
            throw new Error(`${target} 关联的仓库克隆失败，请检查配置或网络`);
        }
        this.logger.info(`代码位置 ${aliasDir}，共 ${aliasRepos.length} 个仓库`);
        this.scheduleOpenIdea(aliasDir);
        return true;
    },

    async cloneByAppName(appName, verbose, cloneOptions = {}) {
        try {
            await this.ensureOpsClient(verbose);
            const appData = await this.opsClient.appEnv(appName);
            const gitSshUrl = this.resolveAppSshUrl(appData);
            if (StrUtil.isNull(gitSshUrl)) {
                this.logger.error(`${appName} 未找到可用的 scm 仓库地址`);
                return false;
            }
            this.logger.info(`即将克隆 ${appName} 仓库: ${gitSshUrl}`);
            return await GitUtil.cloneDirect(gitSshUrl, null, cloneOptions);
        } catch (error) {
            this.logger.error(`${appName} 获取仓库信息失败: ${error.message}`);
            return false;
        }
    },

    resolveAppSshUrl(appData) {
        if (!appData) {
            return '';
        }
        const scm = appData.scm;
        if (typeof scm === 'string') {
            return StrUtil.toSshUrl(scm);
        }
        if (scm && typeof scm === 'object') {
            const candidateKeys = ['ssh', 'git', 'url', 'http'];
            for (const key of candidateKeys) {
                if (StrUtil.hasValue(scm, key)) {
                    return StrUtil.toSshUrl(scm[key]);
                }
            }
        }
        return '';
    },
};
